﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Caching;
using System.Web.Services.Protocols;
using System.Xml.Linq;
using log4net;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.Sdk.Query;
using Microsoft.Crm.SdkTypeProxy;
using TTprotons.Crm.ExternalView.Service.Common;
using TTprotons.Crm.ExternalView.Service.CrmOnlineDiscovery;
using TTprotons.Crm.ExternalView.Service.Settings;
using TTprotons.Crm.ExternalView.Service.Caching;

namespace TTprotons.Crm.ExternalView.Service.Crm
{
    /// <summary>
    /// Query Helper class specific for CRM Online.
    /// </summary>
    public class CrmOnlineQueryManager
    {
        private readonly LiveIdTicketAcquirer ticketAcquirer;
        private static readonly ILog log = LogManager.GetLogger("EventLog");

        #region Constants

        private const string CRMONLINE_PARTNER = "crm.dynamics.com";
        private const string CRMONLINE_ENVIRONMENT = "PROD";
        private const string TICKET_CACHEKEY = "CrmTicket";
        private const string ORG_CACHEKEY = "CrmOrgDetail";

        #endregion

        public CrmOnlineQueryManager()
        {
            ticketAcquirer = new LiveIdTicketAcquirer();
        }

        #region Properties

        public string CrmTicketNumber
        {
            get
            {
                if (WCFCache.Current[TICKET_CACHEKEY] != null)
                    return WCFCache.Current[TICKET_CACHEKEY].ToString();

                var connectResult = Connect();
                if (connectResult.IsSuccess)
                    return connectResult.CrmTicket;

                throw new Exception("Unable to retrieve CRM Ticket." + Environment.NewLine + connectResult.Message);
            }
        }

        public OrganizationDetail OrgDetail
        {
            get
            {
                if (WCFCache.Current[ORG_CACHEKEY] != null)
                    return WCFCache.Current[ORG_CACHEKEY] as OrganizationDetail;

                var connectResult = Connect();
                if (connectResult.IsSuccess)
                    return connectResult.OrgDetail;

                throw new Exception("Unable to retrieve Organization Detail." + Environment.NewLine + connectResult.Message);
            }
        }

        #endregion

        #region Connect method

        public ConnectionResult Connect()
        {
            return Connect(Config.CrmOnlineUserId, Config.CrmOnlinePassword, Config.CrmOnlineOrganization);
        }

        public ConnectionResult Connect(string username, string password, string organization)
        {
            try
            {                
                if (WCFCache.Current[TICKET_CACHEKEY] != null)
                {
                    // No need to retrieve new ticket since we still have a valid ticket in Cache.
                    return new ConnectionResult(true, string.Empty);
                }

                var discoveryService = new CrmDiscoveryService();

                // Retrieve the policy for CRM Online Web Service.
                var policyRequest = new RetrievePolicyRequest();
                var policyResponse = (RetrievePolicyResponse)discoveryService.Execute(policyRequest);

                // Retrieve the WLID Ticket
                var wlidTicket = ticketAcquirer.GetWlidTicket(username, password, CRMONLINE_PARTNER, policyResponse.Policy, CRMONLINE_ENVIRONMENT);

                // Retrieve the CRM Ticket based on the WLID Ticket
                var crmTicketRequest = new RetrieveCrmTicketRequest
                {
                    OrganizationName = organization,
                    PassportTicket = wlidTicket
                };

                var crmTicketResponse = (RetrieveCrmTicketResponse)discoveryService.Execute(crmTicketRequest);

                // Retrieve the Ticket
                var crmTicket = crmTicketResponse.CrmTicket;
                var orgDetail = crmTicketResponse.OrganizationDetail;

                DateTime expirationDate;
                if (DateTime.TryParse(crmTicketResponse.ExpirationDate, out expirationDate))
                {
                    // Cache this ticket for performance purpose.
                    WCFCache.Current.Insert(TICKET_CACHEKEY, crmTicket, expirationDate.AddMinutes(-5));

                    // Cache the Organization Detail for performance purpose.
                    WCFCache.Current.Insert(ORG_CACHEKEY, orgDetail, expirationDate.AddMinutes(-5));
                }

                return new ConnectionResult(true, string.Empty, crmTicket, orgDetail);
            }
            catch (SoapException ex)
            {
                log.Warn("CrmOnlineQueryManager.Connect() - Error occured" + Environment.NewLine + ex.Detail.InnerText, ex);
                return new ConnectionResult(false, ex.Detail.InnerText);
            }
            catch (Exception ex)
            {
                log.Warn("CrmOnlineQueryManager.Connect() - Error occured", ex);
                return new ConnectionResult(false, ex.Message);
            }
        }

        #endregion

        protected CrmService GetCrmService()
        {
            var orgDetail = OrgDetail;
            var ticketNumber = CrmTicketNumber;

            // Create the AuthenticationToken for CRM Online.
            var authToken = new CrmAuthenticationToken
            {
                AuthenticationType = AuthenticationType.Passport,
                CrmTicket = ticketNumber,
                OrganizationName = orgDetail.OrganizationName
            };

            // Create the Crm Service instance
            return new CrmService { Url = orgDetail.CrmServiceUrl, CrmAuthenticationTokenValue = authToken };
        }

        public WhoAmIResponse WhoAmI()
        {
            try
            {
                var crmService = GetCrmService();
                var whoAmIRequest = new WhoAmIRequest();

                var result = (WhoAmIResponse)crmService.Execute(whoAmIRequest);
                return result;
            }
            catch (SoapException ex)
            {
                // Log this error.
                log.Warn("CrmOnlineQueryManager.WhoAmI() - Error occured" + Environment.NewLine + ex.Detail.InnerText, ex);
                return null;
            }
            catch (Exception ex)
            {
                // Log this error.
                log.Warn("CrmOnlineQueryManager.WhoAmI() - Error occured", ex);
                return null;
            }
        }

        public List<DynamicEntity> Query(QueryExpression query)
        {
            try
            {
                var crmService = GetCrmService();

                // Create the Retrieve Multiple Request
                var retrieveRequest = new RetrieveMultipleRequest { Query = query, ReturnDynamicEntities = true };
                var response = (RetrieveMultipleResponse)crmService.Execute(retrieveRequest);

                if (!response.BusinessEntityCollection.IsNullOrEmpty())
                {
                    return (from i in response.BusinessEntityCollection.BusinessEntities
                            select (DynamicEntity)i).ToList();
                }
            }
            catch (SoapException ex)
            {
                // Log this error.
                log.Warn("CrmOnlineQueryManager.Query() - Error occured" + Environment.NewLine + ex.Detail.InnerText, ex);
                return null;
            }
            catch (Exception ex)
            {
                // Log this error.
                log.Warn("CrmOnlineQueryManager.Query() - Error occured", ex);
                return null;
            }

            return null;
        }

        public XElement FetchXml(string fetchXml)
        {
            try
            {
                var crmService = GetCrmService();

                // Run the FetchXml query.
                var result = crmService.Fetch(fetchXml);
                return XElement.Parse(result);
            }
            catch (SoapException ex)
            {
                // Log this error.
                log.Warn("CrmOnlineQueryManager.FetchXml() - Error occured" + Environment.NewLine + ex.Detail.InnerText, ex);
                return null;
            }
            catch (Exception ex)
            {
                // Log this error.
                log.Warn("CrmOnlineQueryManager.FetchXml() - Error occured", ex);
                return null;
            }
        }

        public XElement FetchXml(string fetchXml, int pageNo, int recordCount)
        {
            try
            {
                var origFetchXmlElement = XElement.Parse(fetchXml);
                origFetchXmlElement.SetAttributeValue("count", recordCount);
                var entityElement = origFetchXmlElement.Element("entity");

                if (entityElement == null) return null;

                var counter = 1;
                var fetchXmlTemplate = XElement.Parse(fetchXml);
                string pagingCookie = string.Empty;
                XElement fetchResult;

                do
                {
                    if (counter == 1)
                        fetchResult = FetchXml(origFetchXmlElement.ToString());
                    else
                    {
                        // Setup the FetchXML Query attributes
                        fetchXmlTemplate.SetAttributeValue("page", counter);
                        fetchXmlTemplate.SetAttributeValue("count", recordCount);
                        fetchXmlTemplate.SetAttributeValue("paging-cookie", pagingCookie);

                        fetchResult = FetchXml(fetchXmlTemplate.ToString());
                    }

                    if (pageNo == 1)
                        return fetchResult;

                    if (fetchResult == null || fetchResult.Attribute("morerecords").Value.EqualsIgnoreCase("0"))
                        return null;

                    pagingCookie = fetchResult.Attribute("paging-cookie").Value;
                    counter++;

                } while (counter < pageNo);

                origFetchXmlElement.SetAttributeValue("page", pageNo);
                origFetchXmlElement.SetAttributeValue("count", recordCount);
                origFetchXmlElement.SetAttributeValue("paging-cookie", pagingCookie);

                return FetchXml(origFetchXmlElement.ToString());
            }
            catch (Exception)
            {
                return null;
            }
        }
    }

    public class ConnectionResult
    {
        public ConnectionResult(bool isSuccess, string message)
            : this(isSuccess, message, null, null)
        { }

        public ConnectionResult(bool isSuccess, string message, string crmTicket, OrganizationDetail orgDetail)
        {
            IsSuccess = isSuccess;
            Message = message;
            CrmTicket = crmTicket;
            OrgDetail = orgDetail;
        }

        public bool IsSuccess { get; set; }
        public string Message { get; set; }
        public string CrmTicket { get; set; }
        public OrganizationDetail OrgDetail { get; set; }
    }
}